package org.mbds.wolf.android.idgo800;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;

import org.mbds.wolf.android.tools.FileUtilities;
import org.mbds.wolf.exception.ByteArrayToHexaStringException;
import org.mbds.wolf.seql.APDUModel;
import org.mbds.wolf.seql.AppletModel;
import org.mbds.wolf.seql.ISeqlCallBack;
import org.mbds.wolf.seql.ISeqlController;
import org.mbds.wolf.seql.SeqlMetadata;
import org.mbds.wolf.seql.SeqlParser;
import org.mbds.wolf.seql.SeqlWords;
import org.mbds.wolf.seql.exceptions.APDUError;
import org.mbds.wolf.seql.exceptions.BadRequestException;
import org.mbds.wolf.seql.exceptions.NoReaderException;
import org.mbds.wolf.seql.exceptions.NoSecureElementException;
import org.mbds.wolf.seql.exceptions.SeqlErrorMsgConstants;
import org.mbds.wolf.tools.BytesTool;

import android.content.Context;
import android.util.Log;

import com.gemalto.idgo800.IDGoConstants;
import com.gemalto.idgo800.IDGoMain;

/**
 * 
 * @author alesas
 * 
 * This class is the WOLF/SEQL controller for the IDCore MicroSD accessed with IDGo800 API
 * It is onl suitable for the JavaCard interfcae (not MD)                                               ================
 *                                                
 */
public class  SeqlIDGo800Controller implements ISeqlController {
	
	public final static String DEFAULT_CONFIG_FILE_NAME = "config.xml";
	
	private File config;
	private Context context;
	private ISeqlCallBack callback;
	private boolean isConnected = false;
	private static String currentAID = null;
	private IDGo800APDUHelper m_apduService = null;
	// Application signature provided by Gemalto
	private byte[] m_appSignature;
	
	/**
	 * SeqlIDGo800Controller Constructor requiring config file
	 * @param context
	 * @param callback
	 * @param appSignature
	 * @param configFile
	 * @throws Exception
	 */
	public SeqlIDGo800Controller(Object context, ISeqlCallBack callback, File configFile, String appSignature) throws Exception 
	{
		
		try
		{
			m_appSignature = BytesTool.hexStringToByteArray(appSignature);
		}
		catch (Exception e)
		{
			throw e;
		}
		setConfigFile(configFile);
		setCallback(callback);
		setContext((Context) context);
		initService();
	}
	
	/**
	 * SeqlIDGo800Controller Constructor using assets config file config.xml
	 * @param context
	 * @param callback
	 * @param appSignature
	 * @throws Exception
	 */
	public SeqlIDGo800Controller(Object context, ISeqlCallBack callback, String appSignature) throws Exception 
	{
		this(context, callback, FileUtilities.getFileFromAssets((Context) context, DEFAULT_CONFIG_FILE_NAME), appSignature);
	}

	/**
	 * setContext
	 * @param context
	 */
	private void setContext(Context context) {
		this.context = context;
	}
	
	@Override
	public void setCallback(ISeqlCallBack callback) {
		this.callback = callback;
	}

	@Override
	public void setConfigFile(File configFile) {
		this.config = configFile;
	}

	@Override
	public boolean execute(String command, int commandId) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean initService() {
		if (!isConnected)
		{
			try {
				m_apduService = new IDGo800APDUHelper(context, IDGoConstants.DEVICE_TYPE_AUTO_SELECT, m_appSignature);
				isConnected = m_apduService.isInitialized();
			} catch (IDGo800Exception e) {
				Log.e("initService","APDUHelper not initialized!");
				e.printStackTrace();
			}
		}
		return isServiceConnected();
	}
	
	/**
	 * executeApdu
	 * @param sqlInstruction
	 * @param cmdId
	 * @return
	 */
	public boolean executeApdu(final String sqlInstruction, final int cmdId)
	{
		if (sqlInstruction==null || sqlInstruction.trim().length()==0) 
		{
			return false;
		}
		
		boolean success = false;
		
		try 
		{
			if (!initService())
			{
				return false;
			}
			
	        Map<String, Object> maps = SeqlParser.parseSeql(sqlInstruction,config);
	        
			if (maps == null) 
			{
				if (callback!=null)
				{
					callback.onBadRequest(SeqlErrorMsgConstants.MSG_ERR_INCORRECT_SEQL_INS);
				}
			} 
			else 
			{
				AppletModel appletModel = null;
				Object model = maps.get(SeqlWords.STR_APPLET);
				
				if (model instanceof AppletModel) 
				{
					appletModel = (AppletModel) model;
				}
				
				if (appletModel == null) 
				{
					if (callback!=null)
					{
						callback.onBadRequest(SeqlErrorMsgConstants.MSG_ERR_WRONG_CONFIGURATION);
					}
					return false;
				} 		

				if (currentAID==null || !currentAID.equalsIgnoreCase(((AppletModel)model).getAID()))
				{
				
					//select the applet (initialize the result HashMap)
					success = sayHello(((AppletModel)model).getAlias(), cmdId);
					
					if (!success) 
					{
						if (callback!=null)
						{
							callback.onNotConnected();
						}
						return false;
					}
					
				}
				///////////////////////////
				executeCommand(maps, cmdId);
				///////////////////////////
				success = true;
			}
		} catch (Exception e) 
		{
			if (callback!=null) {
				callback.onBadRequest(e.getMessage());
			}
		}
		return success;		
	}
	
	@Override
	public boolean sayHello(String appletAlias, int commandId) {
		try 
		{
			if (!isServiceConnected())
			{
				return false;
			}
			AppletModel model = SeqlMetadata.getInstance(config).getAppletByAlias(appletAlias);
			currentAID = model.getAID();
			byte[] apdu = APDUModel.getSelectAidApdu(BytesTool.hexStringToByteArray(currentAID));
			
			//////////////////////////////////////////////
			byte[] response = m_apduService.APDU_Transmit(apdu);
			//////////////////////////////////////////////
			
			return APDUModel.isResponseSucceded(response);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
		return false;
	}
	
	@Override
	public boolean isServiceConnected() {
		return isConnected;
	}
	
	@Override
	public void finalize() throws Throwable {
		// Disconnect APDU service
		m_apduService.APDU_Disconnect();
		// Release IDGo800 API
		IDGoMain.release();
		super.finalize();
	}
	
	/**
	 * executeCommand
	 * @param map of APDU commands (Structure: Applet<-Instructions<-Fields<-Commands)
	 * @param commandId
	 * @return boolean
	 * @throws BadRequestException
	 * @throws NoSecureElementException
	 * @throws NoSuchElementException
	 * @throws NoReaderException
	 * @throws IOException
	 */
	boolean executeCommand(Map<String, Object> maps, int commandId) throws 	BadRequestException, 
																			NoSecureElementException,
																			NoSuchElementException, 
																			NoReaderException, 
																			IOException 
	{
		Map<String, Object> response = new HashMap<String, Object>();
		AppletModel applet = null;
		Object a = maps.get(SeqlWords.STR_APPLET);

		if (a instanceof AppletModel) 
		{
			applet = (AppletModel) a;
		}
		
		if (applet == null) 
		{
			callback.onNoApplet();
			return false;
		}
		
		boolean success = false;
		
		byte[][] commands = (byte[][]) maps.get(SeqlWords.STR_COMMAND);
		String[] fields = (String[]) maps.get(SeqlWords.STR_FIELD);
		
		int idxField = 0;
		for (byte[] command : commands) 
		{
			byte[] resp = APDUModel.SW_EXECUTION_ERROR;
			try {
				resp = m_apduService.APDU_Transmit(command);
			} catch (IDGo800Exception e1) {
				Log.e("executeCommand","APDU command failed!");
				e1.printStackTrace();
			}
			
			try 
			{
				System.out.println("Response: "+BytesTool.byteArrayToHexString(resp));
			} 
			catch (ByteArrayToHexaStringException e) 
			{
				//
			}
			if (!APDUModel.isResponseSucceded(resp)) 
			{
				if (APDUModel.isPinRequired(resp)) 
				{
					callback.onPINRequired();
				} 
				else 
				{
					APDUError error = new APDUError("Wrong APDU response", APDUModel.getResponseStatusWord(resp));
					callback.onAPDUError(error);
				}
			} 
			else 
			{
				String r = APDUModel.getResponseBody(resp);
				response.put(fields[idxField], r);
				// Repeat fields loop for each instruction sequence
				idxField = (idxField == fields.length? 0: idxField + 1);
				success = true;
			}

			if (!success) 
			{
				//TODO: rollback all transactions!!!
				break;
			}
		}
		if (success) 
		{
			//System.out.println("Response sending...........");
			callback.onResponse(response, commandId);
		}
		return success;
	}
	
}
